/**
 * 项目版本管理路由
 *
 * Created by snowingsea on 2020/02/11.
 */
// const multer = require('multer');
const compressing = require('compressing');
const fs = require('fs-extra');
const path = require('path');
const moment = require('moment');
const { Types } = require('mongoose');
const randomstring = require('randomstring');
const middleware = require('./../middleware');
const users = require('./../../models/users');
const projectVersions = require('./../../models/project-versions');
const projectHistories = require('./../../models/project-histories');
const projectVersionHistories = require('./../../models/project-version-histories');
const gitClient = require('./../../models/git/git-client');
const PhoenixError = require('./../../models/phoenix-error');
const utility = require('./../../models/utility');
const defects = require('../../models/defects');
const files = require('../../models/files');
const ProjectVersion = require('../../models/mongo/project-version');
const analyzeTemplates = require('../../models/analyze-templates');
const numbers = require('../../models/numbers');
const cacheUtil = require('../../cacheUtil');
const uploadProgress = require('../middleware/upload-progress');
const codeInstrumentations = require('../../models/code-instrumentation');
const ProgressIndicator = require('../../models/progress-indicator');
const { eventHub } = require('../../models/event-hub');
const operationLogs = require('../../models/operation-logs');
/**
 * 获取项目版本信息
 * @param req
 * @param res
 * @param next
 */
function getProjectVersion(req, res, next) {
  const { projectVersionId } = req.params;
  projectVersions.updateVisitTime(projectVersionId, req.$projectVersion, req.$project, req.$me)
    .then(() => {
      req.$projectVersion.visitAt = moment();
      res.json(projectVersions.getJson(req.$projectVersion));
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 批量获取项目版本信息
 * @param req
 * @param res
 * @param next
 */
function getProjectVersions(req, res, next) {
  const resProjectVersions = {
    pagination: req.filter,
  };
  const options = projectVersions.getVersionOptions(req.query);
  options.projectId = req.$project._id; // eslint-disable-line no-underscore-dangle
  options.masterVersionId = null;
  projectVersions.filter(req.filter, options)
    .then((data) => {
      resProjectVersions.versions = data.map((version) => projectVersions.getSummaryJson(version));
      return projectVersions.getCount(options);
    })
    .then((count) => {
      resProjectVersions.pagination.total = count;
      res.json(resProjectVersions);
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 添加项目版本
 * @param req
 * @param res
 * @param next
 */
function addProjectVersion(req, res, next) {
  const {
    versionName,
    projectType,
    language,
    headers,
    languages,
  } = req.query;
  if (!versionName) {
    next(new PhoenixError('invalid request', 'Missing parameters. "versionName" is required'));
    return;
  }
  if (!projectType) {
    next(new PhoenixError('invalid request', 'Missing parameters. "projectType" is required'));
    return;
  }
  if (!language) {
    next(new PhoenixError('invalid request', 'Missing parameters. "language" is required'));
    return;
  }

  if (headers) {
    if (headers === '{}') {
      next(new PhoenixError('invalid request', 'Missing parameters. "headers" is required'));
      return;
    }
    try {
      req.query.headers = JSON.parse(headers);
    } catch (e) {
      next(new PhoenixError('invalid request', `Fail to parse headers to JSON: ${e}`));
      return;
    }
  }
  if (languages) {
    if (languages === '{}') {
      next(new PhoenixError('invalid request', 'Missing parameters. "languages" is required'));
      return;
    }
    try {
      req.query.languages = JSON.parse(languages);
    } catch (e) {
      next(new PhoenixError('invalid request', `Fail to parse languages to JSON: ${e}`));
      return;
    }
  }

  if (req.query.headers && req.query.headers.suffixes) {
    if (!utility.isArrayType(req.query.headers.suffixes)) {
      req.query.headers.suffixes = [req.query.headers.suffixes];
    }
  }

  if (req.query.languages && req.query.languages[language].suffixes) {
    if (!utility.isArrayType(req.query.languages[language].suffixes)) {
      req.query.languages[language].suffixes = [req.query.languages[language].suffixes];
    }
  }

  let projectVersion;
  projectVersions.addVersion(req.params.projectId, req.query, req.$project, req.$project.creator, req.$license)
    .then((res) => {
      projectVersion = res;
      return projectHistories.addHistory(res.projectId, projectHistories.kind.createVersion, res);
    })
    .then(() => {
      res.json(projectVersions.getJson(projectVersion));
    })
    .catch((err) => {
      if (err.code === 11000) {
        err = new PhoenixError('conflict', 'project version already exist');
      }
      next(new PhoenixError(err));
    });
}
/**
 * 添加项目回归版本
 * @param req
 * @param res
 * @param next
 */
async function addProjectRegressionVersion(req, res, next) {
  (async () => {
    const {
      query,
      $project,
      $me,
      file,
    } = req;
    const {
      versionName,
      oldVersionId,
    } = query;
    const { projectId } = req.params;
    const type = Number.parseInt(query.type.toString(), 10);
    if (!versionName) {
      next(new PhoenixError('invalid request', 'Missing parameters. "versionName" is required'));
      return;
    }
    if (!type) {
      next(new PhoenixError('invalid request', 'Missing parameters. "type" is required'));
      return;
    }
    if (!oldVersionId) {
      next(new PhoenixError('invalid request', 'Missing parameters. "oldVersionId" is required'));
      return;
    }
    if (type === files.repositoryType.git || type === files.repositoryType.svn) {
      if (!query.url || query.url === '') {
        next(new PhoenixError('invalid request', 'Missing parameters. "url" is required'));
        return;
      }
    }
    if (type === files.repositoryType.file) {
      if (!file) {
        next(new PhoenixError('invalid request', 'Missing parameters. "file" is required'));
      }
    }
    const zipFileNameEncoding = query.zipFileNameEncoding || 'gb2312';
    const projectVersion = await projectVersions.addRegressionVersion(projectId,
      query, $project, $me);
    const lockKey = numbers.getVersionLockKey(projectVersion.masterVersionId || projectVersion._id);
    await numbers.waitLock(
      lockKey,
      { expireSec: 300, retryInterval: 100, shouldRetry: global.config.auth.multiUserLogin || false }
    );
    req.$lockKey = lockKey;
    await projectHistories.addHistory(projectId,
      projectHistories.kind.createVersion, projectVersion);
    const parentFile = await files.getFile((projectVersion.rootDirectoryId));
    if (type === files.repositoryType.file) { // 文件上传
      const { path, isDir, originalname } = file;
      const prefix = isDir ? originalname : '';
      await files.updateByUploadFile(parentFile._id, path, parentFile,
        projectVersion, $project, $project.creator, null, zipFileNameEncoding, prefix);
    } else {
      query.password = req.body.password;
      await files.updateByRemoteSource(parentFile._id, query.url, query, parentFile,
        projectVersion, $project, $project.creator, type);
    }
    const retFile = await files.getFile((projectVersion.rootDirectoryId));
    if (retFile.extra) { // 拉取源码失败
      await projectVersions.removeVersion(retFile.versionId, projectVersion, $project, $me);
      res.json(retFile);
      return;
    }
    // 同步回归版本子版本数据
    await projectVersions.copyRegressionSubVersionConfig(projectVersion._id, oldVersionId);
    const resJson = files.getJson(retFile);
    resJson.versionId = projectVersion._id;
    res.json(resJson);
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}

/**
 * 删除项目版本
 * @param req
 * @param res
 * @param next
 */
function removeProjectVersion(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const { $projectVersion, $project, $me } = req;
    const relationVersions = await projectVersions.getRelationVersions(projectVersionId);
    await projectVersions.removeVersion(projectVersionId, $projectVersion, $project, $me);
    // 记录主版本删除操作
    const { deleteVersion } = projectHistories.kind;
    await projectHistories.addHistory($projectVersion.projectId, deleteVersion, $projectVersion);
    const promises = [];
    for (let i = 0; i < relationVersions.length; i += 1) {
      const relationVersion = relationVersions[i];
      promises.push(projectVersions.removeVersion(relationVersion._id,
        relationVersion, $project, $me));
    }
    await Promise.all(promises);
    res.status(204).json();
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}

/**
 * 异步删除版本
 * @param {number} index 版本数组下标
 * @param {Array} resVersions 版本数组
 * @param {Project} project 项目
 * @param {User} me 创建者
 * @return {Promise<void>}
 */
function removeVersions(index, resVersions, project, me) {
  return new Promise((resolve, reject) => {
    if (index < resVersions.length) {
      const version = resVersions[index];
      const versionId = version._id; // eslint-disable-line no-underscore-dangle
      index += 1;
      projectVersions.removeVersion(versionId, version, project, me)
        .then(() => removeVersions(index, resVersions, project, me))
        .then(resolve)
        .catch(reject);
    } else {
      resolve();
    }
  });
}

/**
 * 批量删除项目版本
 * @param req
 * @param res
 * @param next
 */
function removeProjectVersions(req, res, next) {
  const options = projectVersions.getVersionOptions(req.query);
  options.projectId = req.$project._id; // eslint-disable-line no-underscore-dangle
  projectVersions.getVersions(req.query, options)
    .then((resVersions) => removeVersions(0, resVersions, req.$project, req.$me))
    .then(() => {
      res.status(204).json();
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 获取项目版本统计信息
 * @param req
 * @param res
 * @param next
 */
function getProjectVersionStatistics(req, res, next) {
  const { projectVersionId } = req.params;
  projectVersions.getProjectVersionStatistics(projectVersionId, req.$projectVersion)
    .then((statistics) => {
      res.json(statistics);
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 获取项目版本压缩包
 * @param req
 * @param res
 * @param next
 */
function getProjectVersionZipPackage(req, res, next) {
  const sourcecodeDir = cacheUtil.getConfigPath('sourcecodeDir');
  let { progressIndicator } = req;
  if (!progressIndicator) {
    // 下载源代码
    progressIndicator = new ProgressIndicator();
    const chan = utility.getSessionId();
    progressIndicator.on('progress', (progress) => {
      if (chan) {
        eventHub.emit(chan, 'export', progress);
      }
    });
  }
  Promise.resolve()
    .then(() => {
      if (req.$me) {
        return Promise.resolve(req.$me);
      }
      return users.getUser(req.$project.creatorId);
    })
    .then((creator) => {
      req.$me = creator;
      return fs.mkdirs(sourcecodeDir);
    })
    .then(() => {
      const { projectName } = req.$project;
      const { versionName } = req.$projectVersion;
      const zipPath = path.join(sourcecodeDir, `${randomstring.generate(16)}.zip`);
      let exportPath;
      gitClient.archive(req.$me, req.$project, req.$projectVersion)
        .then((path) => {
          if (progressIndicator) {
            progressIndicator.advance(0.3);
          }
          exportPath = path;
          if (req.$codeInstrumentationDir) {
            return fs.copy(req.$codeInstrumentationDir, exportPath)
              .then(() => {
                if (progressIndicator) {
                  progressIndicator.advance(0.3);
                }
              })
              .then(() => compressing.zip.compressDir(exportPath, zipPath));
          }
          return compressing.zip.compressDir(exportPath, zipPath)
            .then(() => progressIndicator.advance(0.3));
        })
        .then(() => fs.rmdir(exportPath, { recursive: true }))
        .then(() => fs.stat(zipPath))
        .then((stats) => {
          const totalSize = stats.size;
          res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition');
          const exportZipFileName = encodeURI(`${projectName}-${versionName}.src.zip`);
          if (req.$codeInstrumentationDir) {
            res.download(zipPath, exportZipFileName, () => fs.unlink(zipPath));
          } else {
            res.setHeader('Content-Disposition', `attachment; filename="${exportZipFileName}"`);
            const stream = fs.createReadStream(zipPath);
            stream.on('data', (chunk) => {
              progressIndicator.advance(0.4 * (Math.floor((chunk.byteLength / totalSize) * 100) / 100));
            });
            stream.pipe(res).once('close', () => fs.unlink(zipPath));
          }
        });
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 获取项目版本单条历史信息
 * @param req
 * @param res
 * @param next
 */
function getProjectVersionHistory(req, res, next) { // eslint-disable-line no-unused-vars
  res.json(projectVersionHistories.getJson(req.$projectVersionHistory));
}

/**
 * 获取项目版本历史列表
 * @param req
 * @param res
 * @param next
 */
function getProjectVersionHistories(req, res, next) {
  const resProjectVersionHistories = {
    pagination: req.filter,
  };
  const options = {
    versionId: req.$projectVersion._id, // eslint-disable-line no-underscore-dangle
  };
  projectVersionHistories.filter(req.filter, options)
    .then((data) => {
      resProjectVersionHistories.histories = data.map((a) => projectVersionHistories.getJson(a));
      return projectVersionHistories.getCount(options);
    })
    .then((count) => {
      resProjectVersionHistories.pagination.total = count;
      res.json(resProjectVersionHistories);
    })
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 *
 * @param req
 * @param res
 * @param next
 */
function getOverview(req, res, next) {
  (async () => {
    const { projectVersionId: versionId } = req.params;
    const version = await ProjectVersion.findById(Types.ObjectId(versionId), [
      'projectId', 'versionName', 'coverages',
    ], { lean: true });
    const [overview, testResultOverview, functionCoverage] = await Promise.all([
      projectVersions.getOverview(versionId),
      projectVersions.testResultOverview(versionId),
      projectVersions.functionCoverageDistribution(versionId),
    ]);

    res.json({
      version,
      overview: {
        ...overview,
        numberOfLines: {
          headerFile: overview.headerNumberOfLines,
          sourceFile: overview.sourceNumberOfLines,
        },
        commentLines: {
          headerFile: overview.headerCommentLines,
          sourceFile: overview.sourceCommentLines,
        },
        emptyLines: {
          headerFile: overview.headerEmptyLines,
          sourceFile: overview.sourceEmptyLines,
        },
        headerNumberOfLines: undefined,
        sourceNumberOfLines: undefined,
        headerCommentLines: undefined,
        sourceCommentLines: undefined,
        headerEmptyLines: undefined,
        sourceEmptyLines: undefined,
      },
      testResultOverview,
      functionCoverage,
    });
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

function getSubVersionOverview(req, res, next) {
  (async () => {
    const { projectVersionId: masterVersionId } = req.params;
    const arr = await projectVersions.getRelationVersions(masterVersionId, { includeSelf: true });
    const subVersion = arr
      .find((v) => v.versionType === projectVersions.versionType.integrationTest);
    if (!subVersion) {
      // sub version尚未创建
      return res.json({});
    }
    const versionId = subVersion._id;
    const version = await ProjectVersion.findById(Types.ObjectId(versionId), [
      'projectId', 'versionName', 'coverages',
    ], { lean: true });
    const [overview, testResultOverview, functionCoverage] = await Promise.all([
      projectVersions.getOverview(versionId),
      projectVersions.testResultOverview(versionId),
      projectVersions.functionCoverageDistribution(versionId),
    ]);

    return res.json({
      version,
      overview: {
        ...overview,
        numberOfLines: {
          headerFile: overview.headerNumberOfLines,
          sourceFile: overview.sourceNumberOfLines,
        },
        commentLines: {
          headerFile: overview.headerCommentLines,
          sourceFile: overview.sourceCommentLines,
        },
        emptyLines: {
          headerFile: overview.headerEmptyLines,
          sourceFile: overview.sourceEmptyLines,
        },
        headerNumberOfLines: undefined,
        sourceNumberOfLines: undefined,
        headerCommentLines: undefined,
        sourceCommentLines: undefined,
        headerEmptyLines: undefined,
        sourceEmptyLines: undefined,
      },
      testResultOverview,
      functionCoverage,
    });
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

function getDefectTree(req, res, next) {
  (async () => {
    const { projectVersionId: versionId } = req.params;
    const { level, extensions } = req.query;
    const levels = level ? [].concat(level).map((l) => parseInt(l, 10)) : undefined;
    const ret = await defects.getDefectTree(versionId, {
      levels,
      extensions,
    });

    res.json(ret);
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

function getDefects(req, res, next) {
  (async () => {
    const { projectVersionId: versionId } = req.params;
    const {
      fileId, ruleId, level, status, extensions,
    } = req.query;
    const levels = level ? [].concat(level).map((l) => parseInt(l, 10)) : undefined;
    const defectsList = await defects.getDefects(versionId, {
      fileId,
      ruleId,
      levels,
      status: status ? [].concat(status).map((s) => parseInt(s, 10)) : undefined,
      extensions: extensions ? [].concat(extensions) : undefined,
    });

    res.json({
      defectsList,
    });
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

function updateDefect(req, res, next) {
  (async () => {
    const updates = utility.selectFields(req.body, ['status', 'description']);
    const { defectIds } = req.body;
    await defects.updateDefect(defectIds, updates);
    res.status(204).json();
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

function getDefectsOverview(req, res, next) {
  (async () => {
    const { projectVersionId: versionId } = req.params;
    const ret = await defects.getVersionOverview(versionId);
    res.json(ret);
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

function getOverviewAll(req, res, next) {
  (async () => {
    const { projectVersionId: versionId } = req.params;
    const ret = await projectVersions.getOverviewAll(versionId);
    res.json(ret);
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

function clearDefects(req, res, next) {
  (async () => {
    const { fileId } = req.query;
    const { projectVersionId } = req.params;
    if (fileId) {
      await defects.clearFilesDefects(projectVersionId, fileId);
    } else {
      await defects.clearDefects(projectVersionId);
    }
    res.status(204).json();
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function clearFilesDefects(req, res, next) {
  (async () => {
    const { fileIds } = req.body;
    const { projectVersionId } = req.params;
    if (!fileIds || fileIds.length === 0) {
      throw new PhoenixError('invalid request', 'Missing parameters. fileIds is required');
    }
    // 过滤掉文件夹
    const clearFiles = await files.getFiles(projectVersionId,
      { fileIds },
      { kinds: files.kind.file });
    // 清除文件的缺陷
    await defects.clearFilesDefects(projectVersionId, null, clearFiles);
    res.status(204).json();
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}

function diffProjectVersions(req, res, next) {
  (async () => {
    const { currentVersionId, targetVersionId } = req.query;
    const ret = await projectVersions.diffProjectVersions(req.$me,
      currentVersionId, targetVersionId);
    res.json(ret);
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function getVersionConfig(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const versionConfig = await projectVersions.getVersionConfig(projectVersionId);
    await projectVersions.updateVisitTime(projectVersionId,
      req.$projectVersion, req.$project, req.$me);
    res.json(versionConfig);
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function updateProjectVersion(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const { $projectVersion, $project, $me } = req;
    const { projectId } = $projectVersion;
    const date = moment();
    const visitAt = date;
    const finishAt = date;
    let masterVersionId;
    let unitTestVersion;
    let staticAnalyzeVersion;
    let integrationTestVersion;
    const {
      languages, headers, staticAnalyze, integrationTest, unitTest, versionName, macros,
    } = req.body;
    if (staticAnalyze) {
      if (staticAnalyze.analyzeTemplate) {
        const template = await analyzeTemplates.getTemplate(staticAnalyze.analyzeTemplate, $me._id);
        if (!template) {
          staticAnalyze.analyzeTemplate = null;
        }
      } else {
        staticAnalyze.analyzeTemplate = null;
      }
    }
    if (macros && !utility.isArrayType(macros)) {
      throw new PhoenixError('invalid request', 'macros should be a JSON array');
    }
    if (headers && headers === '{}') {
      throw new PhoenixError('invalid request', 'Missing parameters. "headers" is required');
    }
    if (languages && languages === '{}') {
      throw new PhoenixError('invalid request', 'Missing parameters. "languages" is required');
    }
    const staticAnalyzeVersionFields = {
      exist: false,
      visitAt,
      finishAt,
      versionType: projectVersions.versionType.staticAnalyze,
      ...staticAnalyze,
    };
    const integrationTestVersionFields = {
      exist: false,
      visitAt,
      finishAt,
      versionType: projectVersions.versionType.integrationTest,
      ...integrationTest,
    };
    const unitTestVersionFields = {
      exist: false,
      visitAt,
      finishAt,
      versionType: projectVersions.versionType.unitTest,
      ...unitTest,
    };
    Object.keys(req.body).forEach((key) => {
      if (projectVersions.versionConfigFields.commonFields.includes(key)) {
        staticAnalyzeVersionFields[key] = req.body[key];
        integrationTestVersionFields[key] = req.body[key];
        unitTestVersionFields[key] = req.body[key];
      }
    });
    const relationVersions = await projectVersions.getRelationVersions(
      projectVersionId,
      { includeSelf: true }
    );
    for (let i = 0; i < relationVersions.length; i += 1) {
      const relationVersion = relationVersions[i];
      switch (relationVersion.versionType) {
        case projectVersions.versionType.unitTest:
          unitTestVersionFields.exist = true;
          unitTestVersion = relationVersion;
          masterVersionId = relationVersion._id;
          break;
        case projectVersions.versionType.staticAnalyze:
          staticAnalyzeVersionFields.exist = true;
          staticAnalyzeVersion = relationVersion;
          masterVersionId = relationVersion.masterVersionId;
          break;
        case projectVersions.versionType.integrationTest:
          integrationTestVersionFields.exist = true;
          integrationTestVersion = relationVersion;
          masterVersionId = relationVersion.masterVersionId;
          break;
        default:
          break;
      }
    }
    if (staticAnalyzeVersionFields.exist) {
      await projectVersions.updateVersion(staticAnalyzeVersion._id, staticAnalyzeVersionFields,
        staticAnalyzeVersion, $project, $me);
    } else {
      await projectVersions.addSubVersion(masterVersionId, staticAnalyzeVersionFields);
    }
    if (integrationTestVersionFields.exist) {
      await projectVersions.updateVersion(integrationTestVersion._id, integrationTestVersionFields,
        integrationTestVersion, $project, $me);
    } else {
      await projectVersions.addSubVersion(masterVersionId, integrationTestVersionFields);
    }
    if (unitTestVersionFields.exist) {
      await projectVersions.updateVersion(unitTestVersion._id, unitTestVersionFields,
        unitTestVersion, $project, $me);
    }
    // 处理操作日志
    if (versionName && $projectVersion.versionName !== versionName) {
      const details = {
        originVersionName: $projectVersion.versionName,
        newVersionName: versionName,
      };
      await projectHistories.addHistory(projectId, projectHistories.kind.renameVersion, details);
    }
    const projectVersionConfig = await projectVersions.getVersionConfig(projectVersionId);
    res.json(projectVersionConfig);
  })().catch((err) => {
    if (err.code === 11000) {
      err = new PhoenixError('conflict', 'project version name already exist');
    }
    next(new PhoenixError(err));
  });
}

function initSubVersion(req, res, next) {
  (async () => {
    const { projectVersionId: masterVersionId } = req.params;
    const { versionType } = req.query;
    if (!versionType) {
      throw new PhoenixError('invalid request', 'need versionType');
    }
    const ret = await projectVersions.initSubVersion(masterVersionId, versionType);
    // 拉取最新的源码文件
    if (ret.currentCommitId) {
      await gitClient.checkoutBranch(req.$project.creator, req.$project, ret, ret.currentCommitId);
    }
    res.json({
      versionId: ret._id,
    });
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}
function migrateTestCaseOfVersion(req, res, next) {
  (async () => {
    const { sourceVersionId, functionIds } = req.body;
    const { projectVersionId: targetVersionId } = req.params;
    if (!sourceVersionId) {
      throw new PhoenixError('invalid request', 'need sourceVersionId');
    }
    if (!functionIds || functionIds.length === 0) {
      throw new PhoenixError('invalid request', 'need functionIds');
    }
    const result = await projectVersions.migrateTestCaseOfVersion(sourceVersionId, {
      functionIds,
      creator: req.$me,
      targetVersionId,
      locale: req.$locale,
      isUnitTestToIntegrationTest: true,
    });
    if (!result.exist) {
      throw new PhoenixError('No test case');
    }
    res.json();
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function integrationTestInvokeGraph(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const { versionName } = req.$projectVersion;
    const { extensions } = req.query;

    const sourceExts = Object.values(req.$projectVersion.languages)
      .reduce((arr, item) => arr.concat(item.suffixes), []);
    const headerExts = req.$projectVersion.headers.suffixes;

    const { projectName } = req.$project;
    let extNames;
    if (extensions) {
      switch (extensions) {
        case 'header':
          extNames = headerExts;
          break;
        case 'source':
          extNames = sourceExts;
          break;
        default:
          throw new PhoenixError('invalid request', 'extensions can only be source or header');
      }
    } else {
      extNames = [...headerExts, ...sourceExts];
    }
    const graph = await projectVersions.integrationTestInvokeGraph(projectVersionId, extNames);
    res.json({ ...graph, projectName, versionName });
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function collectCoverage(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const { fileIds, functionIds } = req.body;
    await projectVersions.collectCoverage(projectVersionId, fileIds, functionIds);
    res.json();
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function setTraceabilityCol(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const { commonFields, userDefinedFields } = req.body;
    // console.log(projectVersionId, commonFields, userDefinedFields);
    const resColumns = await projectVersions.setTraceabilityMatrixColumns(projectVersionId, commonFields, userDefinedFields);
    res.json(resColumns);
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function getTraceabilityCol(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const resColumns = await projectVersions.getTraceabilityMatrixColumns(projectVersionId);
    res.json(resColumns);
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function getTraceabilityMatrix(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const { testcaseId } = req.query;
    let { page, perPage = 10 } = req.query;
    perPage = parseInt(perPage, 10);
    if (testcaseId) {
      // 计算页数
      page = await projectVersions.getTraceabilityMatrixTestCasePage(projectVersionId, testcaseId, perPage);
    } else if (page === undefined) {
      throw new PhoenixError('page or testcaseId is needed');
    }
    page = parseInt(page, 10);
    const result = {
      pagination: {},
    };
    result.pagination.page = page;
    result.pagination.perPage = perPage;
    const calData = await projectVersions.getTraceabilityMatrixData(req.$locale, projectVersionId, page, perPage);
    result.pagination.count = calData.count;
    result.tests = calData.tests;
    res.json(result);
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function getTraceabilityMatrixXlsx(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const excelName = req.$project.projectName.concat(' ').concat(req.$projectVersion.versionName).concat('.追溯矩阵.xlsx');
    const [xlsxFilePath, cachePath] = await projectVersions.getTraceabilityMatrixXlsx(projectVersionId, excelName, req.$locale);
    res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition');
    res.download(xlsxFilePath, encodeURI(path.basename(xlsxFilePath)), () => fs.remove(cachePath));
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

function getCodeInstrumentationExecScript(req, res, next) {
  (async () => {
    if (req.$codeInstrumentation.type === codeInstrumentations.type.targetMachine) {
      const dirs = await fs.readdir(req.$codeInstrumentationDir);
      let d = dirs.find((name) => name.startsWith('TestScript'));
      if (!d) {
        throw new Error('testcase scripts for target machine are not found');
      }
      d = path.join(req.$codeInstrumentationDir, d);
      await compressing.zip.compressDir(d, `${d}.zip`);
      res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition');
      res.download(`${d}.zip`, encodeURI(path.basename(`${d}.zip`)));
      return Promise.resolve();
    }
    const progressIndicator = new ProgressIndicator();
    const chan = utility.getSessionId();
    progressIndicator.on('progress', (progress) => {
      if (chan) {
        eventHub.emit(chan, 'export', progress);
      }
    });
    req.progressIndicator = progressIndicator;
    return getProjectVersionZipPackage(req, res, next);
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

/**
 * 导出用户操作日志
 * @param {Object} req
 * @param {Object} res
 * @param {Object} next
 */
function exportOperationLogs(req, res, next) {
  (async () => {
    const { cachePath, reportPath } = await operationLogs.exportOperationLogs(req.$project);
    res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition');
    res.download(reportPath, encodeURI(path.basename(reportPath)), () => fs.remove(cachePath));
  })().catch((err) => next(new PhoenixError(err)));
}
/**
   * Export model definition object.
   */
module.exports = (app, prefix) => {
  const projectVersionsURL = `${prefix}/projects/:projectId/versions.json`;
  const regressionURL = `${prefix}/projects/:projectId/regression/versions.json`;// 回归版本操作URL
  const projectVersionURL = `${prefix}/project-versions/:projectVersionId.json`;
  const versionConfigURL = `${prefix}/project-versions/:projectVersionId/version-config.json`;
  const diffVersionsURL = `${prefix}/project-versions/diff/versions.json`;// 版本之间比较差异
  const statisticsURL = `${prefix}/project-versions/:projectVersionId/statistics.json`;
  const projectVersionZipURL = `${prefix}/project-versions/:projectVersionId.zip`;
  const projectVersionCodeInstrumentationZipURL = `${prefix}/project-versions/:projectVersionId/code-instrumentation/:codeInstrumentationId.zip`;
  const projectVersionHistoryURL = `${prefix}/project-version-histories/:projectVersionHistoryId.json`;
  const projectVersionHistoriesURL = `${prefix}/project-versions/:projectVersionId/histories.json`;
  const projectVersionOverviewURL = `${prefix}/project-versions/:projectVersionId/overview.json`;
  const defectTreeURL = `${prefix}/project-versions/:projectVersionId/defects/tree.json`;
  const defectsURL = `${prefix}/project-versions/:projectVersionId/defects.json`;
  const clearFilesDefectsURL = `${prefix}/project-versions/:projectVersionId/clearFilesDefects.json`;
  const defectsUpdateURL = `${prefix}/project-versions/:projectVersionId/defects/update.json`;
  const defectOverviewURL = `${prefix}/project-versions/:projectVersionId/static-analyze/overview.json`;
  const overviewAllURL = `${prefix}/project-versions/:projectVersionId/overview-all.json`;
  const initSubVersionURL = `${prefix}/project-versions/:projectVersionId/init.json`;
  const integrationTestOverviewURL = `${prefix}/project-versions/:projectVersionId/integration-test/overview.json`;
  const testCasesCopyURL = `${prefix}/project-versions/:projectVersionId/test-cases-copy.json`;
  const invokeGraphURL = `${prefix}/project-versions/:projectVersionId/invoke-graph.json`;
  const collectCoverageURL = `${prefix}/project-versions/:projectVersionId/collect-coverage.json`;
  const upload = uploadProgress;
  const traceabilityMatrixURL = `${prefix}/project-versions/:projectVersionId/traceability-matrix.json`;
  const traceabilityMatrixXlsxURL = `${prefix}/project-versions/:projectVersionId/traceability-matrix.xlsx`;
  const traceabilityMatrixcolumnURL = `${prefix}/project-versions/:projectVersionId/traceability-matrix-column.json`;
  const exportOperationtLogsURL = `${prefix}/projects/:projectId/logs/export.csv`;

  // 获取项目版本缺陷树
  app.get(defectTreeURL, middleware.user.requireTokenUnexpired);
  app.get(defectTreeURL, middleware.user.requireAvailable);
  app.get(defectTreeURL, middleware.user.requireTester);
  app.get(defectTreeURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(defectTreeURL, middleware.project.requireProjectAccess);
  app.get(defectTreeURL, getDefectTree);

  app.get(defectsURL, middleware.user.requireTokenUnexpired);
  app.get(defectsURL, middleware.user.requireAvailable);
  app.get(defectsURL, middleware.user.requireTester);
  app.get(defectsURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(defectsURL, middleware.project.requireProjectAccess);
  app.get(defectsURL, getDefects);

  app.put(defectsUpdateURL, middleware.user.requireTokenUnexpired);
  app.put(defectsUpdateURL, middleware.user.requireAvailable);
  app.put(defectsUpdateURL, middleware.user.requireTester);
  app.put(defectsUpdateURL, middleware.projectVersion.requireProjectVersionExist);
  app.put(defectsUpdateURL, middleware.project.requireProjectAccess);
  app.put(defectsUpdateURL, updateDefect);

  // 清空静态分析结果
  app.delete(defectsURL, middleware.user.requireTokenUnexpired);
  app.delete(defectsURL, middleware.user.requireAvailable);
  app.delete(defectsURL, middleware.user.requireTester);
  app.delete(defectsURL, middleware.projectVersion.requireProjectVersionExist);
  app.delete(defectsURL, middleware.project.requireProjectAccess);
  app.delete(defectsURL, clearDefects);
  // 批量清除文件缺陷数据
  app.post(clearFilesDefectsURL, middleware.user.requireTokenUnexpired);
  app.post(clearFilesDefectsURL, middleware.user.requireAvailable);
  app.post(clearFilesDefectsURL, middleware.user.requireTester);
  app.post(clearFilesDefectsURL, middleware.projectVersion.requireProjectVersionExist);
  app.post(clearFilesDefectsURL, middleware.project.requireProjectAccess);
  app.post(clearFilesDefectsURL, clearFilesDefects);

  app.get(defectOverviewURL, middleware.user.requireTokenUnexpired);
  app.get(defectOverviewURL, middleware.user.requireAvailable);
  app.get(defectOverviewURL, middleware.user.requireTester);
  app.get(defectOverviewURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(defectOverviewURL, middleware.project.requireProjectAccess);
  app.get(defectOverviewURL, getDefectsOverview);

  app.get(overviewAllURL, middleware.user.requireTokenUnexpired);
  app.get(overviewAllURL, middleware.user.requireAvailable);
  app.get(overviewAllURL, middleware.user.requireTester);
  app.get(overviewAllURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(overviewAllURL, middleware.project.requireProjectAccess);
  app.get(overviewAllURL, getOverviewAll);

  // 获取项目信息
  app.get(projectVersionURL, middleware.user.requireTokenUnexpired);
  app.get(projectVersionURL, middleware.user.requireAvailable);
  app.get(projectVersionURL, middleware.user.requireTester);
  app.get(projectVersionURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(projectVersionURL, middleware.project.requireProjectAccess);
  app.get(projectVersionURL, getProjectVersion);

  // 获取项目列表
  app.get(projectVersionsURL, middleware.user.requireTokenUnexpired);
  app.get(projectVersionsURL, middleware.user.requireAvailable);
  app.get(projectVersionsURL, middleware.user.requireTester);
  app.get(projectVersionsURL, middleware.project.requireProjectExist);
  app.get(projectVersionsURL, middleware.project.requireProjectAccess);
  app.get(projectVersionsURL, middleware.splitFilter);
  app.get(projectVersionsURL, getProjectVersions);

  // 比较版本差异
  app.get(diffVersionsURL, middleware.user.requireTokenUnexpired);
  app.get(diffVersionsURL, middleware.user.requireAvailable);
  app.get(diffVersionsURL, middleware.user.requireTester);
  app.get(diffVersionsURL, diffProjectVersions);

  // 添加项目版本
  app.post(projectVersionsURL, middleware.user.requireTokenUnexpired);
  app.post(projectVersionsURL, middleware.user.requireAvailable);
  app.post(projectVersionsURL, middleware.user.requireTester);
  app.post(projectVersionsURL, middleware.project.requireProjectExist);
  app.post(projectVersionsURL, middleware.project.requireProjectAccess);
  app.post(projectVersionsURL, middleware.project.requireProjectAvailable);
  app.post(projectVersionsURL, middleware.projectVersion.parseVersionQuery);
  app.post(projectVersionsURL, middleware.license.requireLicenseExist);
  app.post(projectVersionsURL, addProjectVersion);

  // 添加项目回归版本
  app.post(regressionURL, middleware.user.requireTokenUnexpired);
  app.post(regressionURL, middleware.user.requireAvailable);
  app.post(regressionURL, middleware.user.requireTester);
  app.post(regressionURL, middleware.project.requireProjectExist);
  app.post(regressionURL, middleware.project.requireProjectAccess);
  app.post(regressionURL, middleware.project.requireProjectAvailable);
  app.post(regressionURL, middleware.projectVersion.parseVersionQuery);
  app.post(regressionURL, middleware.progressNotify());
  app.post(regressionURL, upload.single('file'));
  app.post(regressionURL, addProjectRegressionVersion);

  // 编辑项目版本信息
  app.put(projectVersionURL, middleware.user.requireTokenUnexpired);
  app.put(projectVersionURL, middleware.user.requireAvailable);
  app.put(projectVersionURL, middleware.user.requireTester);
  app.put(projectVersionURL, middleware.projectVersion.requireProjectVersionExist);
  app.put(projectVersionURL, middleware.project.requireProjectAccess);
  app.put(projectVersionURL, middleware.project.requireProjectAvailable);
  app.put(projectVersionURL, middleware.projectVersion.requireProjectVersionAvailable);
  app.put(projectVersionURL, middleware.user.getLockByVersion());
  app.put(projectVersionURL, updateProjectVersion);

  // 获取版本配置接口
  app.get(versionConfigURL, middleware.user.requireTokenUnexpired);
  app.get(versionConfigURL, middleware.user.requireAvailable);
  app.get(versionConfigURL, middleware.user.requireTester);
  app.get(versionConfigURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(versionConfigURL, middleware.project.requireProjectAccess);
  app.get(versionConfigURL, middleware.project.requireProjectAvailable);
  app.get(versionConfigURL, middleware.projectVersion.requireProjectVersionAvailable);
  app.get(versionConfigURL, getVersionConfig);

  // 删除项目版本信息
  app.delete(projectVersionURL, middleware.user.requireTokenUnexpired);
  app.delete(projectVersionURL, middleware.user.requireAvailable);
  app.delete(projectVersionURL, middleware.user.requireTester);
  app.delete(projectVersionURL, middleware.projectVersion.requireProjectVersionExist);
  app.delete(projectVersionURL, middleware.project.requireProjectAccess);
  app.delete(projectVersionURL, middleware.project.requireProjectAvailable);
  app.delete(projectVersionURL, middleware.user.getLockByVersion());
  app.delete(projectVersionURL, removeProjectVersion);

  // 批量删除项目版本
  app.delete(projectVersionsURL, middleware.user.requireTokenUnexpired);
  app.delete(projectVersionsURL, middleware.user.requireAvailable);
  app.delete(projectVersionsURL, middleware.user.requireTester);
  app.delete(projectVersionsURL, middleware.project.requireProjectExist);
  app.delete(projectVersionsURL, middleware.project.requireProjectAccess);
  app.delete(projectVersionsURL, middleware.project.requireProjectAvailable);
  app.delete(projectVersionsURL, removeProjectVersions);

  // 获取文件分类统计
  app.get(statisticsURL, middleware.user.requireTokenUnexpired);
  app.get(statisticsURL, middleware.user.requireAvailable);
  app.get(statisticsURL, middleware.user.requireTester);
  app.get(statisticsURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(statisticsURL, middleware.project.requireProjectAccess);
  app.get(statisticsURL, getProjectVersionStatistics);

  // 获取项目版本压缩包
  // app.get(projectVersionZipURL, middleware.user.requireTokenUnexpired);
  // app.get(projectVersionZipURL, middleware.user.requireAvailable);
  // app.get(projectVersionZipURL, middleware.user.requireTester);
  app.get(projectVersionZipURL, middleware.projectVersion.requireProjectVersionExist);
  // app.get(projectVersionZipURL, middleware.project.requireProjectAccess);
  app.get(projectVersionZipURL, middleware.progressNotify([{ name: 'export', scale: 1 }]));
  app.get(projectVersionZipURL, getProjectVersionZipPackage);

  // 获取项目版本代码插桩压缩包
  app.get(projectVersionCodeInstrumentationZipURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(projectVersionCodeInstrumentationZipURL, middleware.projectVersion.requireCodeInstrumentationExist);
  app.get(projectVersionCodeInstrumentationZipURL, middleware.progressNotify([{ name: 'export', scale: 1 }]));
  app.get(projectVersionCodeInstrumentationZipURL, getCodeInstrumentationExecScript);

  // 获取版本单条历史信息
  app.get(projectVersionHistoryURL, middleware.user.requireTokenUnexpired);
  app.get(projectVersionHistoryURL, middleware.user.requireAvailable);
  app.get(projectVersionHistoryURL, middleware.user.requireTester);
  app.get(projectVersionHistoryURL, middleware.projectVersion.requireProjectVersionHistoryExist);
  app.get(projectVersionHistoryURL, middleware.project.requireProjectAccess);
  app.get(projectVersionHistoryURL, getProjectVersionHistory);

  // 获取版本历史列表信息
  app.get(projectVersionHistoriesURL, middleware.user.requireTokenUnexpired);
  app.get(projectVersionHistoriesURL, middleware.user.requireAvailable);
  app.get(projectVersionHistoriesURL, middleware.user.requireTester);
  app.get(projectVersionHistoriesURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(projectVersionHistoriesURL, middleware.project.requireProjectAccess);
  app.get(projectVersionHistoriesURL, middleware.splitFilter);
  app.get(projectVersionHistoriesURL, getProjectVersionHistories);

  // 项目总览
  app.get(projectVersionOverviewURL, middleware.user.requireTokenUnexpired);
  app.get(projectVersionOverviewURL, middleware.user.requireAvailable);
  app.get(projectVersionOverviewURL, middleware.user.requireTester);
  app.get(projectVersionOverviewURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(projectVersionOverviewURL, middleware.project.requireProjectAccess);
  app.get(projectVersionOverviewURL, middleware.project.requireProjectAvailable);
  app.get(projectVersionOverviewURL, middleware.projectVersion.requireProjectVersionAvailable);
  app.get(projectVersionOverviewURL, getOverview);

  // 初始化sub version
  app.post(initSubVersionURL, middleware.user.requireTokenUnexpired);
  app.post(initSubVersionURL, middleware.user.requireAvailable);
  app.post(initSubVersionURL, middleware.user.requireTester);
  app.post(initSubVersionURL, middleware.projectVersion.requireProjectVersionExist);
  app.post(initSubVersionURL, middleware.project.requireProjectAccess);
  app.post(initSubVersionURL, middleware.project.requireProjectAvailable);
  app.post(initSubVersionURL, middleware.projectVersion.requireProjectVersionAvailable);
  app.post(initSubVersionURL, middleware.user.getLockByVersion());
  app.post(initSubVersionURL, initSubVersion);

  // 根据masterVersionId，获取集成测试总览信息
  app.get(integrationTestOverviewURL, middleware.user.requireTokenUnexpired);
  app.get(integrationTestOverviewURL, middleware.user.requireAvailable);
  app.get(integrationTestOverviewURL, middleware.user.requireTester);
  app.get(integrationTestOverviewURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(integrationTestOverviewURL, middleware.project.requireProjectAccess);
  app.get(integrationTestOverviewURL, middleware.project.requireProjectAvailable);
  app.get(integrationTestOverviewURL, middleware.projectVersion.requireProjectVersionAvailable);
  app.get(integrationTestOverviewURL, getSubVersionOverview);

  // 测试用例移植
  app.post(testCasesCopyURL, middleware.user.requireTokenUnexpired);
  app.post(testCasesCopyURL, middleware.user.requireAvailable);
  app.post(testCasesCopyURL, middleware.user.requireTester);
  app.post(testCasesCopyURL, middleware.projectVersion.requireProjectVersionExist);
  app.post(testCasesCopyURL, middleware.project.requireProjectAccess);
  app.post(testCasesCopyURL, middleware.project.requireProjectAvailable);
  app.post(testCasesCopyURL, middleware.projectVersion.requireProjectVersionAvailable);
  app.post(testCasesCopyURL, middleware.user.getLockByVersion());
  app.post(testCasesCopyURL, migrateTestCaseOfVersion);

  // 项目总览函数调用关系图
  app.get(invokeGraphURL, middleware.user.requireTokenUnexpired);
  app.get(invokeGraphURL, middleware.user.requireAvailable);
  app.get(invokeGraphURL, middleware.user.requireTester);
  app.get(invokeGraphURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(invokeGraphURL, middleware.project.requireProjectAccess);
  app.get(invokeGraphURL, middleware.project.requireProjectAvailable);
  app.get(invokeGraphURL, middleware.projectVersion.requireProjectVersionAvailable);
  app.get(invokeGraphURL, integrationTestInvokeGraph);

  // 设置收集覆盖率
  app.post(collectCoverageURL, middleware.user.requireTokenUnexpired);
  app.post(collectCoverageURL, middleware.user.requireAvailable);
  app.post(collectCoverageURL, middleware.user.requireTester);
  app.post(collectCoverageURL, middleware.projectVersion.requireProjectVersionExist);
  app.post(collectCoverageURL, middleware.project.requireProjectAccess);
  app.post(collectCoverageURL, middleware.project.requireProjectAvailable);
  app.post(collectCoverageURL, middleware.projectVersion.requireProjectVersionAvailable);
  app.post(collectCoverageURL, collectCoverage);

  // 设置追溯矩阵显示列
  app.put(traceabilityMatrixcolumnURL, middleware.user.requireTokenUnexpired);
  app.put(traceabilityMatrixcolumnURL, middleware.user.requireAvailable);
  app.put(traceabilityMatrixcolumnURL, middleware.user.requireTester);
  app.put(traceabilityMatrixcolumnURL, middleware.projectVersion.requireProjectVersionExist);
  app.put(traceabilityMatrixcolumnURL, middleware.project.requireProjectAccess);
  app.put(traceabilityMatrixcolumnURL, setTraceabilityCol);

  // 获取追溯矩阵显示列
  app.get(traceabilityMatrixcolumnURL, middleware.user.requireTokenUnexpired);
  app.get(traceabilityMatrixcolumnURL, middleware.user.requireAvailable);
  app.get(traceabilityMatrixcolumnURL, middleware.user.requireTester);
  app.get(traceabilityMatrixcolumnURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(traceabilityMatrixcolumnURL, middleware.project.requireProjectAccess);
  app.get(traceabilityMatrixcolumnURL, getTraceabilityCol);

  // 获取追溯矩阵数据
  app.get(traceabilityMatrixURL, middleware.user.requireTokenUnexpired);
  app.get(traceabilityMatrixURL, middleware.user.requireAvailable);
  app.get(traceabilityMatrixURL, middleware.user.requireTester);
  app.get(traceabilityMatrixURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(traceabilityMatrixURL, middleware.project.requireProjectAccess);
  app.get(traceabilityMatrixURL, getTraceabilityMatrix);

  // 下载追溯矩阵xlsx
  app.get(traceabilityMatrixXlsxURL, middleware.user.requireTokenUnexpired);
  app.get(traceabilityMatrixXlsxURL, middleware.user.requireAvailable);
  app.get(traceabilityMatrixXlsxURL, middleware.user.requireTester);
  app.get(traceabilityMatrixXlsxURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(traceabilityMatrixXlsxURL, middleware.project.requireProjectAccess);
  app.get(traceabilityMatrixXlsxURL, getTraceabilityMatrixXlsx);

  // 导出用户操作日志
  app.get(exportOperationtLogsURL, middleware.user.requireTokenUnexpired);
  app.get(exportOperationtLogsURL, middleware.user.requireAvailable);
  app.get(exportOperationtLogsURL, middleware.project.requireProjectExist);
  app.get(exportOperationtLogsURL, exportOperationLogs);
};
